/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.handler.predicate;

import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

import jakarta.validation.constraints.NotNull;

import org.springframework.web.server.ServerWebExchange;

/**
 * @author Spencer Gibb
 * <p>
 *     AfterRoutePredicateFactory 是 Spring Cloud Gateway 中的一个具体实现类，
 *     用于定义 "在某个时间点之后" 的路由谓词。
 *     它的作用是根据请求的时间戳判断是否满足条件（即当前时间是否在指定时间之后）。
 *     以下是对其功能、设计和使用场景的详细解析。
 *
 * 1. 核心功能
 * 1.1 路由匹配逻辑
 * AfterRoutePredicateFactory 的核心逻辑是：
 * 检查当前时间 (ZonedDateTime.now()) 是否在配置的时间点之后。
 * 如果满足条件，则返回 true，表示请求匹配该路由；否则返回 false。
 * 1.2 配置支持
 * 它继承了 AbstractRoutePredicateFactory，并实现了快捷配置支持。
 * 通过 shortcutFieldOrder 方法定义了快捷配置的字段顺序，允许用户以简化的形式定义参数。
 * 1.3 自定义谓词
 * 它返回一个自定义的 GatewayPredicate 实现，包含以下内容：
 * 匹配逻辑（test 方法）。
 * 配置对象（getConfig 方法）。
 * 可读性描述（toString 方法）。
 *
 *
 * 3. 使用场景
 * 3.1 基于时间的路由控制
 * 开发者可以使用 AfterRoutePredicateFactory 实现基于时间的路由控制。例如：
 * 某个路由仅在特定时间之后生效。
 * 示例：
 * Yaml
 * spring:
 *   cloud:
 *     gateway:
 *       routes:
 *         - id: example_route
 *           uri: http://example.org
 *           predicates:
 *             - After=2025-04-11T17:11:00+08:00[Asia/Shanghai]
 * 在这种情况下，只有当当前时间在 2025-04-11T17:11:00+08:00[Asia/Shanghai] 之后时，请求才会被转发到目标服务。
 * 3.2 动态配置
 * 可以通过 Java DSL 或编程方式动态设置时间点。
 * 示例：
 * Java
 * AfterRoutePredicateFactory factory = new AfterRoutePredicateFactory();
 * AfterRoutePredicateFactory.Config config = new AfterRoutePredicateFactory.Config();
 * config.setDatetime(ZonedDateTime.of(2025, 4, 11, 17, 11, 0, 0, ZoneId.of("Asia/Shanghai")));
 * Predicate<ServerWebExchange> predicate = factory.apply(config);
 * 3.3 快捷配置
 * 支持通过简化的方式定义时间点。
 * 示例：
 * Yaml
 * spring:
 *   cloud:
 *     gateway:
 *       routes:
 *         - id: example_route
 *           uri: http://example.org
 *           predicates:
 *             - After=2025-04-11T17:11:00+08:00[Asia/Shanghai]
 * 4. 关键点总结
 * 4.1 核心职责
 * 判断当前时间是否在指定时间点之后。
 * 支持快捷配置和动态配置。
 * 4.2 扩展性
 * 可以通过实现类似的谓词工厂创建其他基于时间的路由规则（如 BeforeRoutePredicateFactory）。
 * 4.3 使用范围
 * 主要用于基于时间的路由控制。
 * 适用于需要灵活配置和扩展的场景。
 * </p>
 */
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {

	/**
	 * DateTime key.
	 */
	public static final String DATETIME_KEY = "datetime";

	/**
	 * 功能：构造函数调用父类的构造函数，传入配置类 Config。
	 * 场景：
	 * 初始化谓词工厂时，指定配置类类型。
	 */
	public AfterRoutePredicateFactory() {
		super(Config.class);
	}

	/**
	 * 功能：定义快捷配置的字段顺序。
	 * 场景：
	 * 在快捷配置中，datetime 是唯一的参数。
	 * 示例：
	 * spring:
	 *   cloud:
	 *     gateway:
	 *       routes:
	 *         - id: example_route
	 *           uri: http://example.org
	 *           predicates:
	 *             - After=2025-04-11T17:11:00+08:00[Asia/Shanghai]
	 * @return
	 */
	@Override
	public List<String> shortcutFieldOrder() {
		return Collections.singletonList(DATETIME_KEY);
	}

	/**
	 * 功能：根据配置生成一个谓词。
	 * 关键逻辑：
	 * 		获取当前时间（ZonedDateTime.now()）。
	 * 		比较当前时间是否在配置的时间点之后（now.isAfter(config.getDatetime())）。
	 * 		返回布尔值结果。
	 * 扩展功能：
	 * 		提供配置对象（getConfig）。
	 * 		提供可读性描述（toString）。
	 * @param config
	 * @return
	 */
	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange serverWebExchange) {
				final ZonedDateTime now = ZonedDateTime.now();
				return now.isAfter(config.getDatetime());
			}

			@Override
			public Object getConfig() {
				return config;
			}

			@Override
			public String toString() {
				return String.format("After: %s", config.getDatetime());
			}
		};
	}

	/**
	 * 功能：封装谓词的配置参数。
	 * 字段说明：
	 * datetime：指定时间点，必须为 ZonedDateTime 类型。
	 * 使用 @NotNull 注解确保该字段不能为空。
	 */
	public static class Config {

		/**
		 *  带有区时的日期时间:2007-12-03T10:15:30+01:00[Europe/ Paris].
		 */
		@NotNull
		private ZonedDateTime datetime;

		public ZonedDateTime getDatetime() {
			return datetime;
		}

		public void setDatetime(ZonedDateTime datetime) {
			this.datetime = datetime;
		}

	}

}
